package top.codef.sqlfilter;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;

import org.springframework.util.Assert;

import jakarta.persistence.criteria.CommonAbstractCriteria;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Path;
import top.codef.dao.DaoUtils;
import top.codef.enums.OrderEnum;
import top.codef.sqlfilter.functional.FilterCondition;
import top.codef.sqlfilter.functional.GroupCondition;
import top.codef.sqlfilter.functional.JoinCondition;
import top.codef.sqlfilter.functional.OrderCondition;
import top.codef.sqlfilter.functional.SelectCondition;

@SuppressWarnings("unchecked")
public abstract class AbstractFilter<F> {

	protected final List<Element<? extends Object>> updatableList = new LinkedList<>();

	protected final List<FilterCondition> list = new LinkedList<>();

	protected final List<JoinCondition> joinList = new LinkedList<>();

	protected final List<OrderCondition> orderList = new LinkedList<>();

	protected final List<SelectCondition> selectors = new LinkedList<>();

	protected final List<GroupCondition> groupingBy = new LinkedList<>();

	protected HavingCommonFilter havingCommonFilter = null;

	protected Integer limitCount;

	protected Integer limitStart;

	protected String countField;

	public <R, T> SubCommonFilter<R, T> subQ(Class<R> root, Class<T> target, String fieldType) {
		var subFilter = new SubCommonFilter<R, T>(root, target);
		return subFilter;
	}

	public F having(HavingCommonFilter havingCommonFilter) {
		this.havingCommonFilter = havingCommonFilter;
		return (F) this;
	}

	protected <T> boolean checkNotNull(T value) {
		return value != null;
	}

	public void setCountField(String countField) {
		this.countField = countField;
	}

	public List<FilterCondition> getList() {
		return list;
	}

	public List<SelectCondition> getSelectors() {
		return selectors;
	}

	public List<JoinCondition> getJoinList() {
		return joinList;
	}

	public List<OrderCondition> getOrderList() {
		return orderList;
	}

	public List<GroupCondition> getGroupingBy() {
		return groupingBy;
	}

	public String getCountField() {
		return countField;
	}

	public HavingCommonFilter getHavingCommonFilter() {
		return havingCommonFilter;
	}

	public void setHavingCommonFilter(HavingCommonFilter havingCommonFilter) {
		this.havingCommonFilter = havingCommonFilter;
	}

	public <T> F update(String field, T value) {
		updatableList.add(new Element<Object>(field, value));
		return (F) this;
	}

	public List<Element<? extends Object>> getUpdatableList() {
		return updatableList;
	}

	public <T> F eq(String field, T value) {
		if (checkNotNull(value)) {
			list.add((query, builder, path) -> builder.equal(PathUtils.getPath(field, path),
					confirmValue(query, builder, value)));
		}
		return (F) this;
	}

	public <T> F jsonEq(String field, String jsonField, T value) {
		if (checkNotNull(value)) {
			list.add((query, builder, path) -> builder.equal(builder.function("JSON_EXTRACT", String.class,
					PathUtils.getPath(field, path), builder.literal("\"" + jsonField + "\"")), value));
		}
		return (F) this;
	}

	protected <T> Object confirmValue(CommonAbstractCriteria criteria, CriteriaBuilder builder, T value) {
		if (value instanceof SubCommonFilter) {
			return DaoUtils.subCondition(criteria, builder, (SubCommonFilter<?, ?>) value);
		}
		return value;
	}

	public F or(CommonFilter filter) {
		if (filter.list.size() > 0)
			list.add((query, builder, path) -> {
				return builder.or(DaoUtils.seperate(filter, builder, (Path<?>) path, query));
			});
		return (F) this;
	}

	public F eqFeild(String field1, String field2) {
		list.add((query, builder, path) -> builder.equal(PathUtils.getPath(field1, path),
				PathUtils.getPath(field2, path)));
		return (F) this;
	}

	public <T> F neq(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.notEqual(PathUtils.getPath(field, path),
					confirmValue(query, builder, value)));
		return (F) this;
	}

	public F neqField(String field1, String field2) {
		list.add((query, builder, path) -> builder.notEqual(PathUtils.getPath(field1, path),
				PathUtils.getPath(field2, path)));
		return (F) this;
	}

	public F like(String field, String value) {
		if (checkNotNull(value))
			list.add(
					(query, builder, path) -> builder.like((Expression<String>) PathUtils.getPath(field, path), value));
		return (F) this;
	}

	public F isNull(String field) {
		list.add((query, builder, path) -> builder.isNull(PathUtils.getPath(field, path)));
		return (F) this;
	}

	public <T> F isNotNull(String field) {
		list.add((query, builder, path) -> builder.isNotNull(PathUtils.getPath(field, path)));
		return (F) this;
	}

	public <T> F in(String field, Collection<T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> PathUtils.getPath(field, path).in(value.toArray()));
		return (F) this;
	}

	public <T> F notIn(String field, Collection<T> value) {
		list.add((query, builder, path) -> builder.not(PathUtils.getPath(field, path).in(value.toArray())));
		return (F) this;
	}

	public <T extends Comparable<? super T>> F lt(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThan((Expression<T>) PathUtils.getPath(field, path), value));
		return (F) this;
	}

	public <T extends Comparable<? super T>> F lt(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThan((Expression<T>) PathUtils.getPath(field, path),
					DaoUtils.subCondition(query, builder, value)));
		return (F) this;
	}

	public <T extends Comparable<T>> F le(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThanOrEqualTo((Expression<T>) PathUtils.getPath(field, path),
					value));
		return (F) this;
	}

	public <T extends Comparable<? super T>> F le(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThanOrEqualTo((Expression<T>) PathUtils.getPath(field, path),
					DaoUtils.subCondition(query, builder, value)));
		return (F) this;
	}

	public <T extends Comparable<T>> F gt(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.greaterThan((Expression<T>) PathUtils.getPath(field, path),
					value));
		return (F) this;
	}

	public <T extends Comparable<? super T>> F gt(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.greaterThan((Expression<T>) PathUtils.getPath(field, path),
					DaoUtils.subCondition(query, builder, value)));
		return (F) this;
	}

	public <T extends Comparable<T>> F ge(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder
					.greaterThanOrEqualTo((Expression<T>) PathUtils.getPath(field, path), value));
		return (F) this;
	}

	public <T extends Comparable<? super T>> F ge(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.greaterThanOrEqualTo(
					(Expression<T>) PathUtils.getPath(field, path), DaoUtils.subCondition(query, builder, value)));
		return (F) this;
	}

	public <T extends Comparable<T>> F between(String field, T minValue, T maxValue) {
		if (checkNotNull(maxValue) && checkNotNull(minValue)) {
			list.add((query, builder, path) -> builder.between((Path<T>) PathUtils.getPath(field, path), minValue,
					maxValue));
		}
		return (F) this;
	}

	public F orderByAsc(String... fields) {
		Stream.of(fields).forEach(x -> orderList.add((builder, path) -> builder.asc(PathUtils.getPath(x, path))));
		return (F) this;
	}

	public F orderByDesc(String... fields) {
		Stream.of(fields).forEach(x -> orderList.add((builder, path) -> builder.desc(PathUtils.getPath(x, path))));
		return (F) this;
	}

	public F orderBy(String field, OrderEnum orderEnum) {
		if (field == null)
			return (F) this;
		orderList.add((builder, path) -> orderEnum == OrderEnum.ASC ? builder.asc(PathUtils.getPath(field, path))
				: builder.desc(PathUtils.getPath(field, path)));
		return (F) this;
	}

	public F orderBy(SelectCondition condition, OrderEnum orderEnum) {
		orderList.add((builder, path) -> orderEnum == OrderEnum.ASC ? builder.asc(condition.select(builder, path))
				: builder.desc(condition.select(builder, path)));
		return (F) this;
	}

	public F groupBy(String... fields) {
		Stream.of(fields).forEach(x -> groupingBy.add((path, builder) -> PathUtils.getPath(x, path)));
		return (F) this;
	}

	public F groupBy(SelectCondition... selectCondition) {
		Stream.of(selectCondition).forEach(x -> groupingBy.add((path, builder) -> x.select(builder, path)));
		return (F) this;
	}

	public F select(String... selectField) {
		for (String field : selectField)
			selectors.add((builder, path) -> PathUtils.getPath(field, path));
		return (F) this;
	}

	public F select(SelectCondition... elements) {
		for (SelectCondition selectElement : elements) {
			selectors.add(selectElement);
		}
		return (F) this;
	}

	public F select(Collection<String> selectField) {
		select(selectField.toArray(new String[] {}));
		return (F) this;
	}

	public F join(String field, JoinType joinType) {
		joinList.add((criteria, root, builder) -> root.join(field, joinType));
		return (F) this;
	}

	public F join(String field, JoinCommonFilter commonFilter, JoinType joinType) {
		joinList.add((criteria, root, builder) -> DaoUtils.on(commonFilter, root, builder, criteria, joinType));
		return (F) this;
	}

	public F innerJoin(String field) {
		joinList.add((criteria, root, builder) -> root.join(field, JoinType.INNER));
		return (F) this;
	}

	public F innerJoin(JoinCommonFilter commonFilter) {
		joinList.add((criteria, root, builder) -> {
			return DaoUtils.on(commonFilter, root, builder, criteria, JoinType.INNER);
		});
		return (F) this;
	}

	public F leftJoin(String table) {
		joinList.add((criteria, root, builder) -> root.join(table, JoinType.LEFT));
		return (F) this;
	}

	public F leftJoin(JoinCommonFilter commonFilter) {
		joinList.add((criteria, root, builder) -> {
			return DaoUtils.on(commonFilter, root, builder, criteria, JoinType.LEFT);
		});
		return (F) this;
	}

	public F rightJoin(String table) {
		joinList.add((criteria, root, builder) -> root.join(table, JoinType.RIGHT));
		return (F) this;
	}

	public F rightJoin(JoinCommonFilter commonFilter) {
		joinList.add((criteria, root, builder) -> {
			return DaoUtils.on(commonFilter, root, builder, criteria, JoinType.RIGHT);
		});
		return (F) this;
	}

	public F limit(int limit) {
		Assert.isTrue(limit > 0, "limit count must larger then 0");
		limitStart = 0;
		limitCount = limit;
		return (F) this;
	}

	public F limit(int limitStart, int limitCount) {
		Assert.isTrue(limitCount > 0, "limit count must larger then 0");
		this.limitStart = limitStart;
		this.limitCount = limitCount;
		return (F) this;
	}

	public boolean hasLimit() {
		return (F) this.limitStart != null;
	}

	/**
	 * @return the limitCount
	 */
	public Integer getLimitCount() {
		return limitCount;
	}

	/**
	 * @param limitCount the limitCount to set
	 */
	public void setLimitCount(Integer limitCount) {
		this.limitCount = limitCount;
	}

	/**
	 * @return the limitStart
	 */
	public Integer getLimitStart() {
		return limitStart;
	}

	/**
	 * @param limitStart the limitStart to set
	 */
	public void setLimitStart(Integer limitStart) {
		this.limitStart = limitStart;
	}

	public F pageCountField(String field) {
		this.countField = field;
		return (F) this;
	}
}
